iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
佛心分享-SideProject30

30 天開發一款真正能每天使用的散步 App系列 第 8

基礎散步流程(4)——完成散步統計

  • 分享至 

  • xImage
  •  

完整的散步流程只剩下以下兩點還沒完成:

  1. 計算正式的散步統計數據
  2. 繪製散步路線縮圖

今天要先完成第一部分。

散步過程中,畫面上的距離、時間、速度,都是 App 根據目前收到的 GPS 點位即時計算出來的。

但使用者按下結束後,真正保存到資料庫的數據,並不會直接使用畫面上的結果,而是重新由後端計算一次。

為什麼正式統計放在後端?

真正需要保存到資料庫的正式統計,我選擇全部交給後端計算,原因有幾個:

  1. 前端可能還有定位點尚未同步
  2. App 可能切到背景或被系統回收
  3. 未來可能需要從其他裝置查看紀錄
  4. 統計頁、成就等功能都需要使用同一份結果

因此這裡的分工會是:前端負責顯示散步進行中的即時數據,後端則負責產生最後保存下來的正式統計。

結束散步的流程

使用者按下結束後,會先停止接收新的 GPS 點位,再將本地尚未同步的資料送到後端。

流程如下:

按下結束散步
    ↓
停止定位
    ↓
同步剩餘 GPS 點位
    ↓
後端計算正式統計
    ↓
更新散步狀態
    ↓
顯示完成頁

正式統計前,後端會先取得這次散步經過過濾後的有效 GPS 點位,距離與速度等數據都只會使用這些有效點位計算。

計算總距離

總距離不能只拿第一個點和最後一個點來計算。

假設使用者繞著公園走了一圈,最後又回到起點:

A → B → C → D → A

這時起點和終點的位置可能非常接近,如果只計算兩者之間的距離,最後得到的結果甚至可能接近 0 公尺,但使用者實際上已經走完一整圈。

因此,總距離要將每一組相鄰 GPS 點位之間的距離累加起來:

GPS1
 │ 120 m
 ▼
GPS2
 │ 150 m
 ▼
GPS3
 │ 100 m
 ▼
GPS4

這樣從 GPS1 到 GPS4 的總距離就是:

120 + 150 + 100 = 370 m

程式碼如下:

let totalDistance = 0;

for (let index = 1; index < validPoints.length; index += 1) {
  const previousPoint = validPoints[index - 1];
  const currentPoint = validPoints[index];

  totalDistance += calculateDistance(
    previousPoint,
    currentPoint,
  );
}

兩個經緯度之間的距離可以使用 Haversine 公式計算,前幾天已經介紹過,這裡就不再重複。

由於正式統計使用的是經過過濾後的有效點位,因此前面已經排除掉的 GPS 漂移、瞬間跳點等異常資料,也不會被計入最後的距離。

計算總時間與移動時間

散步的總時間可以直接使用結束時間減去開始時間。

但總時間不等於實際移動時間,例如:

走路 20 分鐘 → 休息 10 分鐘 → 再走 20 分鐘

這次散步的結果會是:

  • 總時間:50 分鐘
  • 暫停時間:10 分鐘
  • 移動時間:40 分鐘

因此,平均移動速度應該使用移動時間計算,而不是總時間。

平均移動速度 = 距離(km)÷ 移動時間(h)

假設使用者在 40 分鐘內走了 2 公里,那麼平均移動速度的計算方式為:

40 分鐘 = 40 ÷ 60 = 0.667 小時

2 ÷ 0.667 ≈ 3 km/h

如果把休息時間也一起算進去,得到的平均速度就會比使用者實際走路時更低,因此這裡需要將暫停時間排除。

計算最快速度

除了平均移動速度之外,我也會計算這次散步中的最快速度。

GPS 點位是每隔一段時間記錄一次,因此這裡會利用相鄰兩個有效點位之間的距離與時間,算出每一小段的移動速度:

每段速度 = 相鄰點位之間的距離 ÷ 經過時間

例如:

GPS A
時間:10:00:00

GPS B
時間:10:00:05

兩點距離:8 公尺

代表使用者在這 5 秒內移動了 8 公尺,因此這一段的速度就是:

8 ÷ 5 = 1.6 m/s
1.6 × 3.6 = 5.76 km/h

其中 3.6 是將公尺/秒轉換成公里/小時的換算倍率:

const maxSpeedKilometersPerHour =
  maxSpeedMetersPerSecond * 3.6;

完整程式碼如下:

let maxSpeedMetersPerSecond = 0;

for (let index = 1; index < validPoints.length; index += 1) {
  const previousPoint = validPoints[index - 1];
  const currentPoint = validPoints[index];

  const durationSeconds =
    (currentPoint.recordedAt.getTime() -
      previousPoint.recordedAt.getTime()) /
    1000;

  if (durationSeconds <= 0) {
    continue;
  }

  const distanceMeters = calculateDistance(
    previousPoint,
    currentPoint,
  );

  const segmentSpeed =
    distanceMeters / durationSeconds;

  maxSpeedMetersPerSecond = Math.max(
    maxSpeedMetersPerSecond,
    segmentSpeed,
  );
}

const maxSpeedKilometersPerHour =
  maxSpeedMetersPerSecond * 3.6;

這段程式會依序計算每兩個有效 GPS 點位之間的速度,再取出其中最大的數值,作為這次散步的最快速度。

最後,這次散步會產生以下幾項正式統計:

  • 總距離
  • 總時間
  • 移動時間
  • 暫停時間
  • 平均移動速度
  • 最快速度

後端計算完成後,再將這些結果寫回散步紀錄。

這樣一來不管是完成頁、歷史紀錄,還是之後要做的統計與成就功能,都可以直接使用同一份正式數據。

正式統計完成後,就只剩下將這次走過的路線畫成縮圖。

明天就來分享如何使用 SVG 繪製散步路線縮圖。


上一篇
基礎散步流程(3)——背景定位
下一篇
基礎散步流程(5)——繪製路線簡圖
系列文
30 天開發一款真正能每天使用的散步 App12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言